﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using RFEngine.Audio;
using RFEngine.Content;
using RFEngine.Graphics;

namespace RFEngine
{
    public sealed class EngineServices : IGameComponent, IUpdateable, IDrawable
    {
        #region Fields

        #region Static Fields

        // A Singleton instance of this class is stored as a static member to keep from being Collected by the GarbageCollector.
        private static EngineServices _Instance;

        private static bool _FirstTimeRun = true;
        private static bool _IsInitialized = false;
        private static bool _Paused = false;

        private static TimeManager _TimeManager;
        private static EntityManager _EntityManager;

        private static Game _Game;
        private static GraphicsDevice _GraphicsDevice;

        private static ContentManager _GlobalContentManager;
        private static List<ContentManager> _ContentManagerCollection = new List<ContentManager>();

        public static event Action OnPause;
        public static event Action OnResume;

        #endregion

        #region Interface Member Fields

        private bool _Enabled = true;
        private bool _Visiable = true;

        private int _UpdateOrder = 0;
        private int _DrawOrder = 0;

        private event EventHandler _EnabledChanged;
        private event EventHandler _VisibleChanged;
        private event EventHandler _UpdateOrderChanged;
        private event EventHandler _DrawOrderChanged;

        #endregion

        #endregion

        #region Properties

        #region Static Properties

        /// <summary>
        /// Gets the Graphics Device this game uses.
        /// </summary>
        public static GraphicsDevice GraphicsDevice
        {
            get
            {
                if (_GraphicsDevice == null)
                    _GraphicsDevice = _Game.GraphicsDevice;

                return _GraphicsDevice;
            }
        }

        /// <summary>
        /// Gets a value indicating if EngineServices have been Initialized.
        /// </summary>
        public static bool Initialized
        {
            get { return _IsInitialized; }
        }

        /// <summary>
        /// Gets a value indicating if the EngineServices are Paused.
        /// <remarks>Does Not Stop Non-Engine Managed Threaded Timers.</remarks>
        /// </summary>
        public static bool Paused
        {
            get { return _Paused; }
        }

        /// <summary>
        /// Gets the Globabl Content Managers Name.
        /// </summary>
        public static string GlobalContentManagersName
        {
            get { return _GlobalContentManager.Name; }
        }

        #endregion

        #region Interface Member Properties

        bool IUpdateable.Enabled
        {
            get { return _Enabled; }
        }

        bool IDrawable.Visible
        {
            get { return _Visiable; }
        }

        int IUpdateable.UpdateOrder
        {
            get { return _UpdateOrder; }
        }

        int IDrawable.DrawOrder
        {
            get { return _DrawOrder; }
        }

        event EventHandler IUpdateable.EnabledChanged
        {
            add { _EnabledChanged += value; }
            remove { _EnabledChanged -= value; }
        }

        event EventHandler IDrawable.VisibleChanged
        {
            add { _VisibleChanged += value; }
            remove { _VisibleChanged -= value; }
        }

        event EventHandler IUpdateable.UpdateOrderChanged
        {
            add { _UpdateOrderChanged += value; }
            remove { _UpdateOrderChanged -= value; }
        }

        event EventHandler IDrawable.DrawOrderChanged
        {
            add { _DrawOrderChanged += value; }
            remove { _DrawOrderChanged -= value; }
        }

        #endregion

        #endregion

        #region Methods

        #region Constructor(s)

        private EngineServices(Game game)
        {
            game.Components.Add(this);
        }

        #endregion

        #region Static Methods

        /// <summary>
        /// Intializes the EngineServices for this Game.
        /// </summary>
        /// <param name="game">The Game to initialize the EngineServices for.</param>
        public static void Intialize(Game game)
        {
            if (game == null)
                throw new ArgumentNullException("game", "game is null.");

            _Game = game;
            _Game.IsFixedTimeStep = false;
            _Instance = new EngineServices(game);
            _GlobalContentManager = new ContentManager(GraphicsDevice, "GlobalContentManager");
            _EntityManager = new EntityManager();
            _IsInitialized = true;
        }

        /// <summary>
        /// A Method to Pause the EngineServices. 
        /// Calling this method does NOT gaurantee that All manageble engine entities remain unchanged when the Engine is resumed. 
        /// It's best if you want an object to pause when the Engine is paused add an event handler to EngineServices.OnPause and 
        /// EngineServices.OnResume events. This will help ensure that the Pause Function works for your custom entities managed 
        /// by the engine.
        /// </summary>
        public static void Pause()
        {
            if (!_Paused)
            {
                _Paused = true;

                if (OnPause != null)
                    OnPause();
            }
        }

        /// <summary>
        /// A Method to Unpause the Engine Services.
        /// </summary>
        public static void UnPause()
        {
            if (_Paused)
            {
                _Paused = false;

                if (OnResume != null)
                    OnResume();

                _TimeManager.Update();
            }
        }

        /// <summary>
        /// Method for getting a content manager instance.
        /// </summary>
        /// <param name="contentMangaersName">The name of the contentManger to retrieve, if it doesn't exist, it will be created</param>
        /// <returns>Returns a content manager instance.</returns>
        public static ContentManager GetContentManagerByName(string contentManagersName)
        {
            for (int i = 0; i < _ContentManagerCollection.Count; i++)
                if (_ContentManagerCollection[i].Name == contentManagersName)
                    return _ContentManagerCollection[i];

            _ContentManagerCollection.Add(new ContentManager(GraphicsDevice, contentManagersName));
            return _ContentManagerCollection.Last();
        }

        /// <summary>
        /// Method for Removing a ContentManager from the EngineServices.
        /// </summary>
        /// <param name="contentManagersName">The name of the content manager to remove.</param>
        public static void RemoveContentManager(string contentManagersName)
        {
            if (contentManagersName != _GlobalContentManager.Name)
            {
                int managersIndex = -1;

                for (int i = 0; i < _ContentManagerCollection.Count; i++)
                    if (_ContentManagerCollection[i].Name == contentManagersName)
                    {
                        _ContentManagerCollection[i].Unload();
                        managersIndex = i;
                    }

                if (managersIndex != -1)
                    _ContentManagerCollection.Remove(_ContentManagerCollection[managersIndex]);

                return;
            }

            _GlobalContentManager.Unload();
        }

        /// <summary>
        /// Method for setting the initialized graphics states.
        /// </summary>
        private void SetGraphicsState()
        {
            GraphicsDevice.DeviceReset += new EventHandler(DeviceReset);
            DeviceReset(this, null);

            _FirstTimeRun = false;
        }

        /// <summary>
        /// Method called when the GraphicsDevice is reset for any number of reasons.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DeviceReset(object sender, EventArgs e)
        {
            GraphicsDevice.VertexDeclaration = new VertexDeclaration(GraphicsDevice, SpriteVertex.VertexElements);
            GraphicsDevice.RenderState.AlphaBlendEnable = true;
            GraphicsDevice.RenderState.AlphaTestEnable = true;
            GraphicsDevice.RenderState.DepthBufferWriteEnable = false;
            GraphicsDevice.RenderState.DepthBufferEnable = true;
            GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;
            GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
            GraphicsDevice.SamplerStates[0].MinFilter = TextureFilter.None;
            GraphicsDevice.SamplerStates[0].MipFilter = TextureFilter.None;
            GraphicsDevice.SamplerStates[0].MagFilter = TextureFilter.None;
            GraphicsDevice.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
            GraphicsDevice.SamplerStates[0].AddressV = TextureAddressMode.Wrap;
        }

        #endregion

        #region Interface Members

        /// <summary>
        /// 
        /// </summary>
        void IGameComponent.Initialize() { }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="gameTime"></param>
        void IUpdateable.Update(GameTime gameTime)
        {
            if (_FirstTimeRun)
            {
                _TimeManager = new TimeManager();
                
                SetGraphicsState();
            }

            if (!_Paused)
            {
                _TimeManager.Update();
                _EntityManager.Update();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="gameTime"></param>
        void IDrawable.Draw(GameTime gameTime)
        {
            if (_EntityManager != null)
                _EntityManager.Draw();
            else
                GraphicsDevice.Clear(Color.TransparentBlack);
        }        

        #endregion

        #region Destructor

        ~EngineServices()
        {
            _EntityManager.Destroy();
        }

        #endregion

        #endregion
    }
}
